/*
 * Copyright 2009 (c) Patrick Pietens.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
package indigo.logger
{
	import indigo.logger.errors.LoggerError;
	import indigo.logger.publishers.IPublisher;
	import indigo.logger.publishers.SOSMax;
	import indigo.logger.publishers.Trace;
	import indigo.logger.publishers.Trazzle;
	import indigo.utils.IDisposable;

	import flash.events.Event;
	import flash.utils.Dictionary;
	/**
	 * The <code>Logger</code> class is used to send log, info, debug and error messages to one or more 
	 * publishers. At this moment supported publishers are <code>trace, SOSMax and Trazzle</code>.
	 * 
	 * <p>By default the <code>Logger</code> uses Flash its trace function to log messages. Use the enable
	 * and disable methods to register one or more publishers</p>
	 * 
	 * @example
	 * <code>
	 * <listing>
	 * <pre>
	 * import indigo.logger.debug;
	 * import indigo.logger.info;
	 * import indigo.logger.error;
	 * 
	 * // Logs "foo" and "bar" to all registered publishers
	 * log( "foo", "bar" );
	 * 
	 * // Sends the debug message "foobar" to all registered publishers
	 * debug( "foobar" )
	 * 
	 * // Sends a info message with the title "foo" and message "beyond all recognition" 
	 * // to all publishers
	 * info( "foo", "beyond all recognition" );
	 * 
	 * // Sends an error to all publishers
	 * error( new Error ( "Foobar" ) );
	 * </pre>
	 * </listing>
	 * </code>
	 * 
	 */
	final public class Logger implements IDisposable
	{
		/**
		 * Integer that indicates the info level
		 */
		public static const LEVEL_INFO:int = 1;
		/**
		 * Integer that indicates the log level
		 */
		public static const LEVEL_LOG:int = 2;
		/**
		 * Integer that indicates the debug level
		 */
		public static const LEVEL_DEBUG:int = 3;
		/**
		 * Integer that indicates the error level
		 */
		public static const LEVEL_ERROR:int = 4;
		/**
		 * String that represents the SOSMax publisher
		 */
		public static const PUBLISHER_SOSMAX:String = "SOSMax";
		/**
		 * String that represents Flash its own trace publisher
		 */
		public static const PUBLISHER_TRACE:String = "trace";
		/**
		 * String that represents the Trazzle publisher
		 */
		public static const PUBLISHER_TRAZZLE:String = "trazzle";
		/**
		 * Reference to the Logger
		 */		
		private static var INSTANCE:Logger = null;
		/**
		 * Integer that indicates the total number of messages send by the Logger
		 */		
		private var _messageCount:int = 0;
		/**
		 * List that holds all registered publishers
		 */		
		private var _publisherList:Dictionary = new Dictionary( true );
		/**
		 * Boolean that indicates a timestamp is send with every message 
		 */
		public static var useTimestamp:Boolean = false;

		
		/**
		 * Constructor
		 * @example Test
		 * 
		 */		
		public function Logger( singleton:SingletonEnforcer ):void
		{
			super( );
			this._publisherList[ PUBLISHER_TRACE ] = new Trace;
		}

		
		/**
		 * Sends a message to all registered publishers of the Logger
		 * @param	Integer that indicates the level of the message
		 * @param	String that indicates the title of the message
		 * @param	String that indicates the description of the message 
		 * 
		 */
		private static function sendMessage( level:int, title:String, description:String = null ):void
		{
			for each ( var publisher:IPublisher in Logger.getInstance( )._publisherList )
				publisher.sendMessage( level, getPath( ), title, description );
			
			Logger.getInstance( )._messageCount++;
		}

		
		/**
		 * Logs a message to all registered publishers
		 * @param	String that represents the log message
		 * 
		 */
		public static function log( message:String ):void
		{
			Logger.sendMessage( LEVEL_LOG, message.toString( ) );
		}

		
		/**
		 * Sends an error message to all registerd publishers
		 * @param	Error which will be displayed by the publishers
		 * 
		 */
		public static function error( error:Error ):void
		{
			Logger.sendMessage( LEVEL_ERROR, error.message, error.getStackTrace( ) );
		}

		
		/**
		 * Sends an info message to all registered publishers
		 * @param	String that represents the title of the message
		 * @param	String that indicates the description of the message
		 * 
		 */
		public static function info( title:String, description:String ):void
		{
			Logger.sendMessage( LEVEL_INFO, title, description );
		}

		
		/**
		 * Sends a debug message to all registered publishers
		 * @param	String that represents the message
		 * 
		 */
		public static function debug( message:* ):void
		{
			Logger.sendMessage( LEVEL_DEBUG, message );
		}

		
		/**
		 * Clears all registered publishers
		 * 
		 */
		public static function clear():void
		{
			for each ( var publisher:IPublisher in Logger.getInstance( )._publisherList )
				publisher.clearOutput( );			
			
			Logger.getInstance( )._messageCount = 0;
		}

		
		/**
		 * Registers a publisher to the Logger
		 * @param	String that indicates the publisher
		 * 
		 */
		public static function addPublisher( publisher:String ):void
		{
			var myLogger:Logger = Logger.getInstance( ); 
			if ( !myLogger.outputExists( publisher ) )
			{
				switch ( publisher )
				{
					case PUBLISHER_TRACE: 
						myLogger._publisherList[ PUBLISHER_TRACE ] = new Trace; 
						break;
					case PUBLISHER_SOSMAX: 
						myLogger._publisherList[ PUBLISHER_SOSMAX ] = new SOSMax; 
						break;
					case PUBLISHER_TRAZZLE: 
						myLogger._publisherList[ PUBLISHER_TRAZZLE ] = new Trazzle; 
						break; 
				}
			}
			else
				throw new LoggerError( LoggerError.LOGGER_PUBLISHER_EXISTS );
		}

		
		/**
		 * Removes a publisher from the logger
		 * @param	String that indicates the publisher
		 * 
		 */
		public static function removePublisher( publisher:String ):void
		{	
			var myLogger:Logger = Logger.getInstance( ); 
			if ( myLogger.outputExists( publisher ) )
			{
				var myOutput:IDisposable = myLogger._publisherList[ publisher ] as IDisposable;
				myOutput.dispose( );
				
				myLogger._publisherList[ publisher ] = null;
				delete myLogger._publisherList[ publisher ];
			}
			else
				throw new LoggerError( LoggerError.LOGGER_PUBLISHER_NOT_FOUND );
		}

		
		/**
		 * Checks if a Publisher exists
		 * @param	String that indicates the publisher
		 * @return	Flag indicating the Publisher exists
		 * 
		 */
		private function outputExists( publisher:String ):Boolean
		{
			return this._publisherList[ publisher] != null;
		}

		
		/**
		 * Returns the origin of the message
		 * @param	Path
		 * 
		 */
		private static function getPath():String
		{
			// Throw an error  to generate a stackTrace
			try
			{
				throw new Error( );
			}
			catch ( error:Error )
			{
				// Split the stackTrace and get the full path
				var myStackTrace:String = error.getStackTrace( );
				var myClassPath:String = myStackTrace.split( "\n" )[ 5 ];
								
				var myParts:Array;
				myParts = myClassPath.substring( 4, myClassPath.length ).split( "::" );
				myParts = myParts[ myParts.length - 1 ].split( "/" );
				
				var myClassName:String = myParts.join( "." );
				myClassName = myClassName.substring( 0, myClassName.length - 2 );
			}
			finally
			{
				return myClassName ? myClassName : "";
			}
		}

		
		/**
		 * Integer that indicates the total number of messages send by the Logger
		 * 
		 */		
		public function get messageCount():int
		{
			return this._messageCount;
		}

		
		/**
		 * Enables the SOSMax publisher
		 * 
		 */
		public static function enableSOSMax():void
		{
			addPublisher( PUBLISHER_SOSMAX );
		}

		
		/**
		 * Disables the SOSMax publisher
		 * 
		 */
		public static function disableSOSMax():void
		{
			removePublisher( PUBLISHER_SOSMAX );
		}

		
		/**
		 * Enables the Flash IDE trace publisher
		 * 
		 */		
		public static function enableTrace():void
		{
			addPublisher( PUBLISHER_TRACE );
		}

		
		/**
		 * Disables the Flash IDE trace publisher
		 * 
		 */				
		public static function disableTrace():void
		{
			removePublisher( PUBLISHER_TRACE );
		}		

		
		/**
		 * Enables the Flash IDE trace publisher
		 * 
		 */				
		public static function enableTrazzle():void
		{
			addPublisher( PUBLISHER_TRAZZLE );
		}

		
		/**
		 * Disables the Flash IDE trace publisher
		 * 
		 */			
		public static function disableTrazzle():void
		{
			removePublisher( PUBLISHER_TRAZZLE );
		}

		
		/**
		 * Returns the instance of the Logger. When there is no reference it
		 * a new instance is created.
		 * @return	Instance of the Logger
		 * 
		 */
		public static function getInstance():Logger
		{
			if ( INSTANCE == null )
				INSTANCE = new Logger( new SingletonEnforcer );
			
			return INSTANCE;
		}

		
		/**
		 * @inheritDoc
		 * 
		 */
		public function dispose( event:Event = null ):void
		{
			// Disable all publishers
			disableSOSMax( );
			disableTrace( );
			disableTrazzle( );
			
			// Nullify properties
			Logger.getInstance( )._publisherList = null;
			INSTANCE = null;
		}
	}
}


class SingletonEnforcer 
{
}